<?
/**
 * 	The general database class.
 *
 *	@copyright	2010 - oneDot only<br>
 *  You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.<br>
 *  The content is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.  see the GNU General Public License for more details.
 *
 *	@author		Koen Betsens <we@onedotonly.be>
 *  @package	Global
 */

/**
 * The general database class.
 *
 * All database calls are happening trough the DB object. The DB session is sand-boxes, so multiple connections are possible.
 * The main concept of this approach is the flexibility of a database-vendor independent structure.  
 *
 * <code>
 * global $DB = new DB( dbhost, dbuser, dbpass, dbname )
 * </code>
 *
 * @package		Global
 *
 * @return		Object	DB session
 */
class DB{
	
	const Q_types		= "select ID, `table`, `key`, meta from types where active=1 order by `key`";
	const Q_settings	= "select `key`, value from settings where `key` in ('%0')";
	const Q_module		= "select json from moddump where ID='%0'";
	
	/**
	 * Constructs the Database object
	 *
	 * @param 	string 	$dbhost		The MySQL server. It can also include a port number. e.g. "hostname:port" or a path to a local socket e.g. ":/path/to/socket" for the localhost
	 * @param 	string 	$dbuser		The username
	 * @param 	string 	$dbpass		The password
	 * @param 	string 	$dbname		The name of the database that is to be selected.
	 * @return	object
	 */
 	function __construct( $dbhost, $dbuser, $dbpass, $dbname ){
		
		$this->dbname = $dbname;
		
		// ESTABLISH CONNECTION
		if( ! $this->hd = mysql_connect ( $dbhost, $dbuser, $dbpass ) )
			exit();
			
		if( ! mysql_select_db( $dbname, $this->hd ) )
			exit();
		
		
		// START CACHING CYCLES
		$this->prepare_typeset();
		
		
		// BUILDING IMPLEMENTED CLASSES
		$this->tags = new DB_TAGS();

	}
	
	//////////////////// PUBLIC DB FUNCTIONS
	
	/**
	 * Bare database request
	 *
	 * Returns unparsed query result, if requested.
	 *
	 * @param 	string  $querystring		query string
	 * @param 	int  	$return		return query result
	 * @return	resource	the unfiltered query result, if $return is true
	 */	
	public function query ( $querystring, $return = 1 ) {

		if( ( $response = mysql_query( $querystring , $this->hd )) && $return )
			return $response;
	}
	
	/**
	 * Bare database request and associative return
	 *
	 * Returns unparsed query result, if requested.
	 *
	 * @param 	string  $query				query string
	 * @param 	mixed  	$return_type		MYSQL_ASSOC (default) or MYSQL_ROW
	 * @return	array	the query result, in rows
	 */	
	public function query_rows ( $query, $return_type = MYSQL_ASSOC ) {

		if( $response = $this->query( $this->pregmatch( $query )))
			while( $row = mysql_fetch_array( $response, $return_type )) $print[] = $row;
		
		return $print;
	}
	

	/**
	 * Bare but sugared query
	 *
	 * Returns single value, if requested.
	 * The function is sugared, ie "<%..%>" will work.
	 *
	 * @param 	string  $query		query string
	 * @return	string
	 */	
	public function query_direct ( $query ) {
		
		if( $response = mysql_fetch_array( $this->query( $this->pregmatch( $query )), MYSQL_NUM ))
			return $response[ 0 ];
	}

	
	/**
	 * Returns array with associative object rows
	 *
	 * This is, next to get(), the most common select function.
	 * If $type is array, the multiple tables are aliassed with the typename. If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	mixed 	$type		the object type string, or array of types
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @return	array	
	 */
	public function filter ( $type, $condt = "active = 1" ) {
		
		$table	= $this->type( $type, "table", 1 );

		$response = $this->query( $this->pregmatch( "select *, <%lang:nameid%> as name from $table where " . $this->condtstring( $type, $condt )));
		while( $row = mysql_fetch_array( $response, MYSQL_ASSOC)) $print[] = $row;
		
		return $print;
	}


	/**
	 * Returns object rows with specified columns
	 *
	 * Returning array is associated with $column incase "simple" request (compared by "simplestring"). If $column is array, rows are associated with current (usually first) value.
	 * If $type is array, the multiple tables are aliassed with the typename. If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	mixed 	$type		the object type string, or array of types
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @param 	mixed 	$column		column key(s)
	 * @return	array	
	 */
	public function filter_key ( $type, $condt = "active = 1", $column = "ID" ) {

		$table = $this->type( $type, "table", 1 ); 
	
		if( is_array( $column )) {
			$key = current( $column );
			$column = implode( ", ", $column );
		} else if( $column == str_replace( array(" ", "."), "", $column) ) $key = $column;

		if( $response = $this->query( $this->pregmatch( "select $column from $table where " . $this->condtstring( $type, $condt ))))
			while( $row = mysql_fetch_array( $response, MYSQL_ASSOC)) if( isset( $key ))
				$print[ $row[ $key ]] = $row; else $print[] = $row;
		
		return $print;
	}


	/**
	 * Returns array with secified column
	 *
	 * Is stripped down version of filter_key
	 * If $type is array, the multiple tables are aliassed with the typename. If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	mixed 	$type		the object type string, or array of types
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @param 	mixed 	$column		column key(s)
	 * @return	array	
	 */
	public function filter_direct ( $type, $condt = "active", $column = "ID" ) {

		$table = $this->type( $type, "table", 1 ); 

		if( $response = $this->query( $this->pregmatch( "select $column from $table where " . $this->condtstring( $type, $condt ))))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = $row[ 0 ];
		
		return $print;
	}


	/**
	 * Returns array with all corresponding objects
	 *
	 * If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	string 	$type		the object type string
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @return	array	array with Objects	
	 */
	public function filter_objects ( $type, $condt = "active" ) {

		$table = $this->type( $type, "table" ); 

		if( $response = $this->query( $this->pregmatch( "select ID from $table where " . $this->condtstring( $type, $condt ))))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = new OBJECT( $type, $row[ 0 ]);
		
		return $print;
	}


	/**
	 * Returns array with all corresponding page objects
	 *
	 * If $condt is an integer, it is interpreted as ID.
	 * 
	 * @deprecated	the core DB class should not be object type dependent
	 * @todo	Should be reassigned
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @return	array	array with Pages	
	 */
	public function filter_pages ( $condt = "active" ) {

		$table = $this->type( "page", "table" ); 

		if( $response = $this->query( "select ID from $table where " . $this->condtstring( "page", $condt )))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = new PAGE(( int ) $row[ 0 ]);
		
		return $print;
	}


	/**
	 * Returns single row
	 *
	 * This is, next to filter(), the most common select function.
	 * If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	string 	$type		the object type string
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @return	array	single row or empty array
	 */
	public function get ( $type, $condt, $key = "*" ) {
		
		$table	= $this->type( $type, "table" );
		
		$response = @$this->query( $this->pregmatch( "select $key from $table where " . $this->condtstring( $type, $condt )));
		return ( $response )? mysql_fetch_array( $response, MYSQL_ASSOC) : array();
	}


	/**
	 * Returns single specified column value
	 *
	 * In opposite to filter_key(), only the first column value will be returned.
	 * If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	string 	$type		the object type string
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @param 	mixed 	$column		column key, defaults to ID
	 * @return	string	specified column value
	 */
	public function get_key ( $type, $condt, $column = "ID" ) {
		
		$table	= $this->type( $type, "table" );
		
		if( $response = $this->query(  $this->pregmatch("select $column from $table where " . $this->condtstring( $type, $condt ) . " limit 1" )));
			$res = mysql_fetch_array( $response, MYSQL_NUM);
		
		return $res[ 0 ];

	}


	/**
	 * Returns single object
	 *
	 * In opposite to filter_objects(), only the the (single) most corresponding object will be returned.
	 * If $condt is an integer, it is interpreted as ID.
	 *
	 * @param 	string 	$type		the object type string
	 * @param 	mixed 	$condt		the conditionals ("where ...")
	 * @return	object
	 */
	public function get_object ( $type, $condt = "active" ) {
		
		$table = $this->type( $type, "table" ); 

		if(( $response = $this->query( $this->pregmatch( "select ID from $table where " . $this->condtstring( $type, $condt ) . " limit 1")))
		&&  $row = mysql_fetch_array( $response, MYSQL_NUM ))
			return new OBJECT( $type, $row[ 0 ]);	
		
	}


	/**
	 * Associative matches
	 *
	 * Returns array with all associative matches from the reference table.<br>
	 * At least 1 type (parent or child) should be declared. If parent/child type and associated ID are declared, only the corresponding child/parent (respectivly) ID's will be returned. If less parameters are declared, all column values will be returned. 
	 *
	 * @param 	mixed  	$parenttype		string or array declaring parent type(s)
	 * @param 	mixed  	$pid_or_condt	integer parent ID or string with conditionals
	 * @param 	mixed  	$objtype		string or array declaring object type(s)
	 * @param 	int  	$objid			child ID
	 * @param 	string  $tablestring	reference table: "object" (default) or "social"
	 * @return	array
	 */
	public function reference ( $parenttype = false, $pid_or_condt = false, $objtype = false , $objid = false, $tablestring = "object" ) {

		
		if( $parenttype )	$conditionals[] = "parenttype " . (is_string( $parenttype )? "= " : "" ) . $this->type( $parenttype, "ID", 1 );
		if( $objtype )		$conditionals[] = "objtype " . (is_string( $objtype )? "= " : "" ) . $this->type( $objtype, "ID", 1 );
		
		if( $conditionals ) {
		
			if( $objid )		$conditionals[] = "objid =" . (int) $objid;
			if( $pid_or_condt )	$conditionals[] = is_int( $pid_or_condt )? "parentid =". $pid_or_condt: $pid_or_condt;
			
			$select = ( $slim = count( $conditionals ) >= 3 )? (( $pid_or_condt )? "objid" : "parentid" ): "parenttype, objtype, parentid, objid";
			
			if( $response = @$this->query( "select $select from glue_$tablestring where ". implode( " && ", $conditionals )))
				while( $row = mysql_fetch_array( $response, $slim? MYSQL_NUM : MYSQL_ASSOC ))
					$print[] = $slim? $row[ 0 ] : $row;
			
			return $print;
		}
	}


	/**
	 * Count(*) request
	 *
	 * Returns count as integer.
	 * If $type is array, the multiple tables are aliassed with the typename.
	 *
	 * @param 	mixed 	$type		the object type string, or array of types
	 * @param 	string 	$condt		the conditionals ("where ...")
	 * @return	int
	 */
	public function count ( $type, $condt = "active=1", $referential = false ) {
		
		$table = ( $refential )? "glue_$referential" : $this->type( $type, "table", 1 );
		
		return  (int) $this->query_direct( "select count(*) from $table where " .
				(( $referential )? "parent" : "" ) . $this->condtstring( $type, $condt ));
	}


	/**
	 * Translate type
	 *
	 * Returns a typename based column value.
	 * Defaults in returning the type ID, used as reference troughout the database. The string type name is to make the whole Engine process more readable, and the types more flexible (not ID dependant).
	 *
	 * @param 	mixed 	$type		the object type string, or array of types
	 * @param 	string 	$key		the type value
	 * @param 	bool 	$is_sql		refers to DB::types() $is_sql, if $type is array
	 * @return	mixed	if $type is a string, value string is returned, if it is an array, the resulting DB::types() array is returned 
	 */
	public function type ( $type, $key = "ID", $is_sql = false ) {
	
		return is_array(  $type )?
			$this->types( $type , $key, $is_sql ) : $this->typelist[ $type ][ $key ];
	}


	/**
	 * Translate typeID
	 *
	 * Reversed DB::type().
	 * Defaults in returning the type name string, used as human-readable reference.
	 *
	 * @param 	int 	$typeid		the object type ID
	 * @param 	string 	$key		the type value
	 * @return	string 
	 */
	public function type_r ( $typeid, $key = "key" ) {
		
		return $this->typelist_reverse[ (int) $typeid ][ $key ];
	}


	/**
	 * Translate multiple types
	 *
	 * Returns an array of typename based column values.
	 * Defaults in returning the type IDs, used as reference troughout the database.
	 *
	 * @param 	array 	$array		array of object type strings
	 * @param 	string 	$key		the type value
	 * @param 	bool 	$is_sql		if true, return a sql string
	 * @return	mixed	returns an array or part of sql query string 
	 */
	public function types ( $array, $key = "ID", $is_sql = false ) {

		if( $array ) foreach( $array as $typename )
			$return[ $typename ] = $this->typelist[ $typename ][ $key ];
			
		if( $is_sql )
			if( $key == "table" ) {

				foreach( $return as $key => $table ) $return[ $key ] = "$table as $key";
				$return = implode( ",", $return );

			} else $return = "in(" . implode( ",", $return ) . ")";
		
		return $return;
	}


	//////////////////// PUSH QUERIES (INSERT / UPDATE / DELETE)
	
	/**
	 * Insert entry
	 *
	 * Inserts (or updates) new entry.
	 * Returns the new or modified entry ID.
	 *
	 * @param 	string 	$type		object typename string
	 * @param 	array 	$input		the columns to be inserted
	 * @param 	array 	$unique		check if entry already exists, by 1 or more parameters
	 * @return	int		the new or modified entry ID 
	 */
	public function set ( $type, $input, $unique = false ) {
		
		//// check if entry exists...
		if( $unique &&
			$id = $this->get_key( $type, $this->sqlimplode( $unique )))
				
				return $this->update( $type, (int) $id, $input );

		//// or continue.
		//// stringify meta, insert type id, user and prepare input
		$input[ 'type' ]	= $this->type( $type );
		
		if( $input[ 'meta' ]) $input[ 'meta' ] = json_encode( $input[ 'meta' ]); 
		
		//if( $this->column_exists( $type, "creator", 1 )) $input[ 'creator' ] = $Engine->user();

		foreach( $input as $k => $v ) $values[ $k ] = $this->smartq( $v );

		
		//// and insert new entry.
		$this->query( "insert into ". $this->type( $type, "table" ) ."(`". implode( "`,`", array_keys( $values )) ."`) values ( ". implode( ", ", $values ) ." )", 0 );
		
		return (int) $this->get_key( $type, $this->sqlimplode( $input ));
	}

	
	/**
	 * Update entry
	 *
	 * Updates existing entry. Json conditionals can be searched by dot notation (ie meta.key.childkey = value). 
	 * Returns the modified entry ID.
	 *
	 * @todo	Meta search should be regexp'd
	 *
	 * @param 	string 	$type		object typename string
	 * @param 	mixed 	$condt		check if entry already exists, by id or array with parameters
	 * @param 	array 	$input		the columns to be inserted/updated
	 * @return	int		the new or modified entry ID 
	 */
	public function update ( $type, $condt, $input ) {
		
		if( is_array( $condt ))
			foreach( $condt as $key => $value )
			
				if( $pos = strpos( $key, "." )) {
					$tail .= " && (" . substr( $key, 0, $pos ) . " like '%\"" . substr( $key, $pos ) . "\":\"$value\"%' or " . substr( $key, 0, $pos ) . " like '%\"" . substr( $key, $pos ) . "\":$value%')";
					unset( $condt[ $key ]);
				}
		
		$identify = is_int( $condt )? "ID = $condt" :
			( $condt )? $this->sqlimplode( $condt ) : "ID > 0";
		

		if( $input[ "meta" ] && $meta = $this->get_key( $type, $identify, "meta" ))
			$input[ "meta" ] = array_merge( json_decode( $meta, true ), $input[ "meta" ]);
			
		$input[ "meta" ] = json_encode( $input[ "meta" ] );
		
		
		$this->query( "update ". $this->type( $type, "table" ) ." set ". $this->sqlimplode( $input, ", " ) . " where $identify $tail", 0 );
		
		return $this->get_key( $type, $identify . $tail );
	}
	
	
	/**
	 * Delete entry
	 *
	 * Deletes or inactivates entries, based on given conditionals.  
	 * Is limited to 1 deletion per call, for safety.
	 *
	 * @todo	incase $safe, multiple "deletes" should be allowed
	 *
	 * @param 	string 	$type		object typename string
	 * @param 	mixed 	$condt		entry conditionals, id or array
	 * @param 	bool 	$safe		incase safe, entry inactivates (not destroyed), defaults safe.
	 * @return	mixed	Incase safe deletion (inactivate), returns the affected ID 
	 */	
	public function delete ( $type, $condt, $safe = true ) {

		$conditional = is_int( $condt )? "ID= $condt" : $this->sqlimplode( $condt );

		//// check if entry is active...
		if( $safe &&
			$this->get_key( $type, $conditional, "active" ))
				
				return $this->update( $type, $condt, array( "active"=> 0 ));
				
		//// or remove.
		$this->query( "delete from ". $this->type( $type, "table" ) ." where $conditional order by ID limit 1", 0 );
	}
	
	
	/**
	 * Insert reference
	 *
	 * Checks for accidental duplication, inserts new reference entry when unique.
	 * Returns true if succesfull.
	 *
	 * @param 	string 	$parenttype	parent reference typename string
	 * @param 	int 	$parentid	parent reference id
	 * @param 	string 	$objtype	child reference typename string
	 * @param 	int 	$objid		child reference id
	 * @param 	string 	$tablestring	reference table name, defaults to "object"
	 * @return	bool	succesfull insert
	 */	
	public function set_reference ( $parenttype, $parentid, $objtype, $objid, $tablestring = "object" ) {
		global $Engine;
		
		if( !count( $this->reference( $parenttype, (int) $parentid, $objtype, (int) $objid, $tablestring ))) {
		
			$this->query( "insert into glue_$tablestring ( `parenttype`, `parentid`, `objtype`, `objid`, `rank`, `user`) values ( {$this->type( $parenttype )}, $parentid, {$this->type( $objtype )}, $objid, 5, '".$Engine->user()."' )", 0 );
			
			$succes = true;
		}
		
		return $succes;
	}
	

	/**
	 * Delete reference(s)
	 *
	 * Deletes entry references, one or multiple.  
	 * there is no safety built in, so be cautious.
	 *
	 * @todo	build in basic safety be requiring minimal 3 paramaters and a limit in $safe mode 
	 *
	 * @param 	string 	$parenttype	parent reference typename string
	 * @param 	int 	$parentid	parent reference id
	 * @param 	string 	$objtype	child reference typename string
	 * @param 	int 	$objid		child reference id
	 * @param 	string 	$tablestring	reference table name, defaults to "object"
	 * @return	int		the number of deleted references
	 */	
	public function delete_reference( $parenttype, $parentid = 0, $objtype = 0, $objid = 0, $tablestring = "object" ) {
	
		if( $parenttype )	$condt[ "parenttype" ]	= $this->type( $parenttype );
		if( $objtype ) 		$condt[ "objtype" ]		= $this->type( $objtype );
		if( $parentid ) 	$condt[ "parentid" ] 	= (int) $parentid;
		if( $objid ) 		$condt[ "objid" ]		= (int) $objid;
		
		if( $n = count( $this->reference( $parenttype, (int) $parentid, $objtype, (int) $objid, $tablestring )))
			
			$this->query( "delete from glue_$tablestring where " . $this->sqlimplode( $condt ), 0 );
		
		return $n;
	}


	/**
	 * Check column
	 *
	 * Checks for column existance in given table or entry type related table.
	 * Returns true if exists.
	 *
	 * @param 	string 	$table_or_type		table name or object typename
	 * @param 	string 	$column				column to be checked
	 * @param 	bool 	$translate_table	if true, $table_or_type behaves as typename (from wich table name should be derived)
	 * @return	bool	column existance
	 */	
	public function column_exists( $table_or_type, $column, $translate_table = false ) {
		
		if( $translate_table ) $table_or_type = $this->type( $table_or_type , "table" );
		
		return isset( 	$this->tablelist[ $table_or_type ][ "columns" ][ $column ]) ?
						$this->tablelist[ $table_or_type ][ "columns" ][ $column ]
				:		$this->tablelist[ $table_or_type ][ "columns" ][ $column ] = ( mysql_fetch_array( $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table_or_type' AND COLUMN_NAME = '$column'" ), MYSQL_NUM ));

	}

	
	//////////////////// PUBLIC LOCAL FUNCTIONS (non query)
	
	/**
	 * visualise querystring
	 *
	 * @todo	Rethink this
	 *
	 * @param 	string 	$type		object typename string
	 * @param 	mixed 	$condt		id or conditonals string
	 * @param 	string 	$columns	the columns to be selected 
	 * @return	string	the pregmatched query string
	 */	
	public function print_query ( $type, $condt, $columns = "*" ) {
	
		$table	= $this->type( $type, "table", 1 );
		return $this->pregmatch( "select $columns, <%lang:nameid%> as name from $table where " . $this->condtstring( $type, $condt ));	
	}
	
	/**
	 * Smart quote
	 *
	 * prepares a sql value for safety and basic syntax. Can be specified as integer (int) or "simple", wich only allows limited set of characters. A regexp conditional is also possible!
	 *
	 * @param 	mixed 	$value		value string or integer
	 * @param 	mixed 	$secondary	specification - can be regexp conditional 
	 * @return	mixed	returns an integer or the parsed string in single quotes
	 */	
	function smartq ( $value, $secondary = false ){

		if( $secondary ) $value = 
			( $secondary == "int" )?	(int) $value : (
			( $secondary == "simple")?	$this->simplestring( $value ) : $this->pregstring( $value, $secondary ) );
				
		return is_numeric( $value )? $value : "'" . mysql_real_escape_string( $value ) . "'";
	}
	
	/**
	 * simple string
	 *
	 * String with limited set of allowed characters ( [^\w\s\.\-\@] )
	 *
	 * @param 	string	$string	the string
	 * @return	string	returns the parsed string
	 */	
	function simplestring( $string ) {
		return preg_replace( "/[^\w\s\.\-\@]/", "", $string );
	}
	
	
	/**
	 * regexp string
	 *
	 * limit set of allowed characters in string, by regexp replace
	 *
	 * @param 	string	$string		the original string
	 * @param 	regexp	$regexp		the regexp parameter
	 * @param string	$replace	compromised characters should be replaced with
	 * @return	string	returns the parsed string
	 */	
	function pregstring( $string, $regexp, $replace = "" ) {
		return preg_replace( $regexp, $replace, $string );
	}
	
	
	/**
	 * serialize sql "WHERE" string
	 *
	 * returns a `key` = "value" pairs string
	 *
	 * @param 	array	$value		array with pairs
	 * @param 	string	$seperator	the seperator string, defaults to " && "
	 * @return	string	returns the parsed string
	 */	
	function sqlimplode( $values, $seperator = " && " ) {
		
		foreach( $values as $key => $value )
			$values[ $key ] = "`$key` = ". $this->smartq( $value );
		
		return implode( $seperator, $values );
	}
	
	
	/**
	 * default sql "WHERE" conditionals string
	 *
	 * the typical object based where 
	 *
	 * @param 	mixed	$type		typename string or array of typenames
	 * @param 	mixed	$condt		id integer or conditionals string
	 * @return	string	parsed string
	 */	
	function condtstring( $type, $condt ) {
	
		return "type " . (is_string( $type )? "= " : "" ) . $this->type( $type, "ID", 1 ) . " && " . ( is_int( $condt )? "ID =". $condt: $condt); 
	}
	

	//////////////////// INTERNAL FUNCTIONS
	
	/**
	 * private function
	 *
	 * prepares the type() and types() function
	 * @access	private
	 */	
	private function prepare_typeset () {
		
		$response = $this->query( self::Q_types );
		while( $type = mysql_fetch_array( $response, MYSQL_ASSOC)) {
			$this->typelist[ $type['key'] ] = $type;
			$this->typelist_reverse[ (int) $type['ID']] = $type;
		}
	}
	
	/**
	 * private function
	 *
	 * subfunction anchors
	 * @access	private
	 */	
	private function pregmatch ( $string ){
		if( strpos( $string, "%" ) && preg_match_all( '/(<%)([^%]*)(%>)/', $string, $raw, PREG_SET_ORDER )) {
			$replace = $replace_by = array();
			foreach( $raw as $v ) if( !in_array( $v[0], $replace )) {
				$replace[] = $v[0];
				$function = explode( ":", $v[2] );
				switch( $function[0] ) {
					case "lang":
						$replace_by[] = $this->lang_construct( $function[1], isset( $function[2] )? $function[2] : "short");
						break;
					case "type":
						$replace_by[] = $this->type( $function[1] );
						break;
					case "table":
						$replace_by[] = $this->type( $function[1], "table" );
						break;
					case "meta":
						$replace_by[] = "meta regexp '(\"{$function[1]}\":[\"]{0,1}{$function[2]}[\",}]{1,})'";
						break;
					case "user":
						$replace_by[] = "(select usertable.user from user as usertable where usertable.ID = {$function[1]} && active limit 1)";
						break;
					case "hasref":
						$replace_by[] = "(select glue_object.objid from glue_object where glue_object.parenttype = type && glue_object.parentid = ID && glue_object.objtype = {$this->type( $function[1] )} limit 1)";
						break;
				}
			}
			$string = str_replace( $replace, $replace_by, $string);
		}
		return $string;
	}
	
	/**
	 * private function
	 *
	 * language subfunction
	 * @access	private
	 */
	private function lang_construct ( $id, $column="short" ) {
		
		$select = ( $lang = engine_get('lang.master') == LANG ) ?
			"='". LANG ."'" : "IN('". LANG ."','". engine_get('lang.master') ."') ORDER BY lang ".((LANG > engine_get('lang.master'))? "DESC":"");
		return "(select lang_construct.$column from txtdump as lang_construct where lang_construct.textid=$id && lang_construct.active=1 && lang_construct.lang $select  limit 1)";
	}

}


/**
 * Tag based DB subclass
 *
 * @deprecated	is used on OBJECT level
 */	
class DB_TAGS {
	function __construct() {
			
	}
		
	public function filter ( $parenttype, $parentid ) {
		global $DB;
		
		if( $response = $DB->query( "select distinct glue_tag.objid from glue_tag where glue_tag.parenttype={$DB->type($parenttype)}" . (( $parentid )? " && glue_tag.parentid= $parentid" : " limit 1000" ) . " group by objid" ))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = new TAG((int) $row[ 0 ]);
		
		return $print;

	}
	
	public function filter_ids ( $parenttype, $parentid ) {
		global $DB;
		
		if( $response = $DB->query( "select glue_tag.objid from glue_tag where glue_tag.parenttype={$DB->type($parenttype)}" . (( $parentid )? " && glue_tag.parentid= $parentid" : " limit 1000" )))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = $row[ 0 ];
		
		return $print;
	}
	
	public function filter_ids_deep ( $parenttype, $parentid ) {
		global $DB;
		
		$tagids = $this->filter_ids( $parenttype, $parentid );
		
		if( $children = $DB->reference( $parenttype, (int) $parentid ))
			foreach( $children as $child )
				if( $ids = $this->filter_ids_deep( $objtype, $objid )) $tagids = array_merge( $tagids, $ids ); 		
		
		return $tagids;
	}
	
	public function deep ( $parenttype, $parentid ) {
		global $DB;
		
		if( $tagids = $this->filter_ids_deep( $parenttype, $parentid ))
			$tagids = array_unique( $tagids, SORT_NUMERIC );

		if( $tagids && $response = $DB->query( "select ID from mod_tag where ID in( " . implode( ",", $tagids ) . " )" ))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = new TAG((int) $row[ 0 ]);
		
		return $print;
	}
	
	public function random ( $limit ) {
		global $DB;
		
		if( $response = $DB->query( "select ID from mod_tag order by rand() limit $limit" ))
			while( $row = mysql_fetch_array( $response, MYSQL_NUM ))
				$print[] = new TAG((int) $row[ 0 ]);
		
		return $print;
	}
			
}







?>